package com.ada.base.security.service.impl;

import com.ada.base.common.PageResult;
import com.ada.base.security.CurrentUserContext;
import com.ada.base.security.dto.PasswordDto;
import com.ada.base.security.entity.User;
import com.ada.base.security.entity.UserRole;
import com.ada.base.security.entity.res.UserCodeAndDesc;
import com.ada.base.security.exception.ModifyPasswordFailedException;
import com.ada.base.security.selma.UserSelmaMapper;
import com.ada.base.security.service.UserService;
import com.ada.base.security.entity.SysUser;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.xebia.extras.selma.Selma;
import org.springframework.jdbc.core.*;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements UserService, UserDetailsService {
    private PasswordEncoder passwordEncoder;
    private JdbcTemplate jdbcTemplate;
    private UserSelmaMapper userSelmaMapper;
    private FindByIndexNameSessionRepository sessionRepository;

    public UserServiceImpl(PasswordEncoder passwordEncoder, JdbcTemplate jdbcTemplate, FindByIndexNameSessionRepository sessionRepository) {
        this.passwordEncoder = passwordEncoder;
        this.jdbcTemplate = jdbcTemplate;
        this.sessionRepository = sessionRepository;
        this.userSelmaMapper = Selma.mapper(UserSelmaMapper.class);
    }

    @Override
    public UserDetails loadUserByUsername(String username) {
        String sql = "select id, username,password,status,clientId,phone,reallyName,company,logo from user where phone=?";
        List<User> userList = jdbcTemplate.query(sql, new Object[]{username}, new RowMapperResultSetExtractor<>(new BeanPropertyRowMapper<>(User.class), 1));

        if (CollectionUtils.isEmpty(userList)) {
            throw new UsernameNotFoundException(username);
        }

        User user = userList.get(0);
        if (user.getStatus() != 1) {
            throw new DisabledException("用户已不在职");
        }
        String query = "select c.code,c.description from user_role a inner join role_authority b on a.roleId=b.roleId " +
                "inner join authority c on b.authorityId = c.id where a.userId = ? and c.clientId=?";
        List<UserCodeAndDesc> authorities = jdbcTemplate.query(query, new Object[]{user.getId(), user.getClientId()}, new RowMapperResultSetExtractor<>(new BeanPropertyRowMapper<>(UserCodeAndDesc.class), 1));
        if (CollectionUtils.isEmpty(authorities))
            authorities = new ArrayList<>();
        authorities.forEach(userCodeAndDesc -> {
            String code = userCodeAndDesc.getCode();
            if (code.startsWith("GROUP_")) {
                userCodeAndDesc.setCode(code + "_" + user.getClientId());
            }
        });

        SysUser sysUser = new SysUser(user.getId(), user.getPhone(), user.getPassword(), authorities.stream().map(UserCodeAndDesc::getCode).map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
        sysUser.setClientId(user.getClientId());
        sysUser.setReallyName(user.getReallyName());
        sysUser.setCompany(user.getCompany());
        sysUser.setLogo(user.getLogo());
        sysUser.setRouter(authorities.stream().filter(userCodeAndDesc -> !StringUtils.isEmpty(userCodeAndDesc.getDescription())).map(userCodeAndDesc ->
                {
                    try {
                        return new ObjectMapper().readValue(userCodeAndDesc.getDescription(), Object.class);
                    } catch (IOException e) {
                        e.printStackTrace();
                        return null;
                    }
                }
        ).collect(Collectors.toList()));
        return sysUser;
    }

    @Override
    public PageResult userList(long pageNum, int pageSize) {
        long starter = (pageNum - 1) * pageSize;
        PageResult pageResult = new PageResult();
        String queryAll = "select * from user";
        RowCountCallbackHandler callbackHandler = new RowCountCallbackHandler();
        jdbcTemplate.query(queryAll, callbackHandler);
        long total = callbackHandler.getRowCount();
        pageResult.setCurrentPage(pageNum);
        pageResult.setTotal(total);
        pageResult.setSize(pageSize);
        long pages;

        if (total % pageSize == 0) {
            pages = total / pageSize;
        } else {
            pages = total / pageSize + 1;
        }
        pageResult.setTotalPage(pages);

        String sql = "select * from user order by id asc limit " + starter + " , " + pageSize;
        List<User> userList = jdbcTemplate.query(sql, new RowMapperResultSetExtractor<>(new BeanPropertyRowMapper<>(User.class), 1));
        pageResult.setData(userSelmaMapper.asUserOutDtos(userList));
        return pageResult;
    }

    @Override
    public void allocRole(final List<UserRole> userRoles) {
        if (CollectionUtils.isEmpty(userRoles))
            return;
        Long userId = userRoles.get(0).getUserId();
        String del = "delete from user_role where userId= ?";
        jdbcTemplate.update(del, userId);
        addRole(userRoles, userId);
    }

    @Override
    public void allocRole(final List<UserRole> userRoles, int type) {
        if (CollectionUtils.isEmpty(userRoles))
            return;
        Long userId = userRoles.get(0).getUserId();
        String del = "delete from user_role where userId= ? and roleId in " +
                "(select id from role where type=? and clientId=?)";
        jdbcTemplate.update(del, userId, type, CurrentUserContext.clientId());
        addRole(userRoles, userId);
    }

    private void addRole(List<UserRole> userRoles, long userId) {
        String insert = "insert into user_role(userId, roleId) values(?,?)";
        jdbcTemplate.batchUpdate(insert, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                UserRole userRole = userRoles.get(i);
                ps.setLong(1, userRole.getUserId());
                ps.setLong(2, userRole.getRoleId());
            }

            @Override
            public int getBatchSize() {
                return userRoles.size();
            }
        });
        new Thread(() -> {
            Map<String, ? extends Session> sessions = findSessionByPrinciple(userId);
            sessions.keySet().forEach(s -> sessionRepository.deleteById(s));
        }).start();
    }

    private Map<String, ? extends Session> findSessionByPrinciple(long userId) {
        String sql = "select phone from user where id=?";
        String phone = jdbcTemplate.queryForObject(sql, new Object[]{userId}, new SingleColumnRowMapper<>(String.class));
        return sessionRepository.findByPrincipalName(phone);
    }

    @Override
    public int add(User user) {
        String createUser = "insert into user(staffNo, username, phone,password, clientId) values(?,?,?,?)";
        return jdbcTemplate.update(createUser, user.getStaffNo(), user.getUsername(), user.getPhone(), passwordEncoder.encode(user.getPassword()), CurrentUserContext.clientId());
    }

    @Override
    public boolean modifyPassword(PasswordDto passwordDto) {
        String newPasswordRepeat = passwordDto.getNewPasswordRepeat();
        String newPassword = passwordDto.getNewPassword();
        String oldPassword = passwordDto.getOldPassword();
        if (!newPassword.equals(newPasswordRepeat))
            throw new ModifyPasswordFailedException("两次输入的新密码不同");
        if (newPassword.equals(oldPassword))
            throw new ModifyPasswordFailedException("新密码不可与原密码相同");

        SysUser current = CurrentUserContext.current();
        Long id = current.getId();
        String sql = "select id, username,password from user where id=?";
        User user = jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<>(User.class));
        if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
            throw new ModifyPasswordFailedException("原密码错误");
        }
        String encode = passwordEncoder.encode(newPassword);
        String update = "update user set password=? where id=?";
        jdbcTemplate.update(update, encode, id);
        return true;
    }

    @Override
    public List<User> queryByAuthority(String code) {
        String query = "select u.* from user u inner join user_role a on u.id=a.userId inner join role_authority b on a.roleId=b.roleId " +
                "inner join authority c on b.authorityId = c.id where c.code = ? and u.clientId=?";
        return jdbcTemplate.query(query, new Object[]{code, CurrentUserContext.clientId()}, new RowMapperResultSetExtractor<>(new BeanPropertyRowMapper<>(User.class), 1));
    }
}
